package repository

import (
	"context"
	"database/sql"
	"log"
	"sync"

	"server/components/logger"
	"server/components/models/ent"

	"entgo.io/ent/dialect"
	entsql "entgo.io/ent/dialect/sql"
	"entgo.io/ent/dialect/sql/schema"
	_ "github.com/jackc/pgx/v5/stdlib"
	"golang.org/x/crypto/bcrypt"
)

const (
	DefaultPGUrl = "postgres://postgres:postgres@localhost:5432/postgres"
)

var (
	_sqlDb     *sql.DB
	_init      = &sync.Once{}
	_entClient *ent.Client
)

// InitConnection инициализирует соединение с базой и формирует таблицы
func InitConnection(ctx context.Context, url string, clean bool) error {
	_init.Do(func() {
		db, err := sql.Open("pgx", url)
		if err != nil {
			logger.Logger().Fatalf("Cannot connect to the database %v %v", url, err)
		}
		_sqlDb = db
		if clean {
			if err := cleanupSQL(ctx); err != nil {
				logger.Logger().Fatalf("Cannot cleanup database %v", err)
			}
		}
		drv := entsql.OpenDB(dialect.Postgres, _sqlDb)
		_entClient = ent.NewClient(ent.Driver(drv))
		if err := _entClient.Schema.Create(ctx,
			schema.WithDropIndex(true),
			schema.WithDropColumn(true)); err != nil {
			logger.Logger().Fatalf("Failed to initialize scheme %v", err)
		}
		// Generate initial user
		if _, err := _entClient.User.Get(ctx, 1); ent.IsNotFound(err) {
			hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
			if err != nil {
				logger.Logger().Fatalf("Cannot generate password hash %v", err)
			}
			if _, err := _entClient.User.Create().
				SetID(1).
				SetPassword(hashedPassword).
				SetEmail("admin@it-planet.org").
				SetFirstName("admin").
				SetLastName("admin").Save(ctx); err == nil {
				logger.Logger().Info("Created default user")
			} else {
				logger.Logger().Errorf("Cannot create default user %v", err)
			}
		}
		logger.Logger().Info("DB initialized")
	})
	return nil
}

func cleanupSQL(ctx context.Context) error {
	log.Println("Performing DB cleanup...")
	const cleanTables = `
DO $$ DECLARE
r RECORD;
BEGIN
    FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP
            EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
        END LOOP;
END $$
	`
	_, err := _sqlDb.ExecContext(ctx, cleanTables)
	if err != nil {
		return err
	}
	_, err = _sqlDb.ExecContext(ctx, `SELECT lo_unlink(l.oid) FROM pg_largeobject_metadata l;`)
	if err != nil {
		return err
	}
	logger.Logger().Info("DB cleanup SUCCESS")
	return nil
}

// CloseConnection закрывает соединение с базой
func CloseConnection(ctx context.Context) error {
	if _sqlDb == nil {
		return nil
	}
	return _sqlDb.Close()
}

// EntClient returns current ent client
func EntClient() *ent.Client {
	return _entClient
}
